package org.xteam.commandline;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

public class CommandLine {
	
	public static final String OPTION_PREFIX = "--";

	private List<OptionAdapter> options;
	private List<String> remainings;
	private Object obj;

	public CommandLine(Object obj) {
		this.obj = obj;
		options = new ArrayList<OptionAdapter>();
		remainings = new ArrayList<String>();
		build();
	}

	private void build() {
		for (Field field : obj.getClass().getFields()) {
			Option option = field.getAnnotation(Option.class);
			if (option == null)
				continue;
			addOption(field, option);
		}
	}

	private void addOption(Field field, Option option) {
		Class type = field.getType();
		if (type == Boolean.TYPE) {
			options.add(new OptionAdapter(option.name(), option.help(), false, field, obj) {
				public void setOption(String arg) {
					boolean value = true;
					if (Boolean.parseBoolean(arg))
						value = true;
					try {
						field.setBoolean(obj, value);
					} catch (Exception e) {
						// ignore
					}
				}
			});
		} else if (type == String.class) {
			options.add(new OptionAdapter(option.name(), option.help(), true, field, obj));
		} else {
			System.err.println("option type not handled " + option.name()
					+ " -> " + field);
		}
	}
	
	private static class OptionAdapter {
		
		private String name;
		private String help;
		private boolean needArgument;
		protected Field field;
		protected Object obj;

		public OptionAdapter(String name, String help, boolean needArgument, Field field, Object obj) {
			this.name = name;
			this.help = help;
			this.needArgument = needArgument;
			this.field = field;
			this.obj = obj;
		}
		
		public void setOption(String arg) {
			try {
				field.set(obj, arg);
			} catch (Exception e) {
				// ignore
			}
		}

		public String getDisplayString() {
			StringBuffer buffer = new StringBuffer();
			buffer.append(OPTION_PREFIX);
			buffer.append(name);
			if (needArgument)
				buffer.append(" <arg>");
			return buffer.toString();
		}
		
	}
	
	public void parse(String[] options) throws OptionException {
		for (int i = 0 ; i < options.length; ++i) {
			String option = options[i].trim();
			if (option.startsWith(OPTION_PREFIX)) {
				String name, arg = null;
				int end = option.indexOf('=');
				if (end > 0) {
					name = option.substring(2, end);
					arg = option.substring(end + 1);
				} else {
					name = option.substring(2);
				}
				OptionAdapter adapter = findAdapter(name);
				if (adapter.needArgument && arg == null) {
					if (i+1 >= options.length)
						throw new OptionException("option " + adapter.name
								+ " need an argument");
					arg = options[++i];
				}
				adapter.setOption(arg);
			} else {
				remainings.add(option);
			}
		}
	}

	private OptionAdapter findAdapter(String name) throws OptionException {
		List<OptionAdapter> matching = new ArrayList<OptionAdapter>();
		for (OptionAdapter adapter : options) {
			if (adapter.name.startsWith(name))
				matching.add(adapter);
		}
		if (matching.isEmpty())
			throw new OptionException("bad option : " + name);
		if (matching.size() > 1)
			throw new OptionException("ambiguous option: " + name);
		return matching.get(0);
	}
	
	public String[] getRemainingOptions() {
		return (String[]) remainings.toArray(new String[remainings.size()]);
	}
	
	public void usage() {
		int nameLength = 0;
		for (OptionAdapter adapter : options) {
			String displayString = adapter.getDisplayString();
			if (displayString.length() > nameLength)
				nameLength = displayString.length();
		}
		System.out.println("Options:");
		for (OptionAdapter adapter : options) {
			String displayString = adapter.getDisplayString();
			System.out.print("\t" + displayString);
			int spaces = nameLength - displayString.length() + 1;
			while(spaces-- > 0)
				System.out.print(' ');
			System.out.println(adapter.help);
		}
	}

}
